package MathCore;

import MathCore.Operands.Constant;
import java.util.EmptyStackException;
import java.util.Enumeration;
import java.util.Stack;
import java.util.Vector;
import MathCore.Operands.List;
import MathCore.Operands.Variable;
import MathCore.Operators.Function;
import MathCore.Operators.OperatorException;

public class Processor extends Object
{
	public Processor(){}

	public static boolean DEBUG=false;
	
	public Vector variables = new Vector();

	public Variable addVariable(Variable var)
	{
		if(var == null) return var;

		variables.addElement(var);

		return var;
	}

	public Variable removeVariable(Variable var)
	{
		if(var == null) return var;

		if(variables.removeElement(var)) return var;
		else return null;
	}
	
	public Variable getVariable(String name)
	{
		Variable var;
		for(Enumeration e=variables.elements(); e.hasMoreElements();)
		{
			var = (Variable) e.nextElement();
			if(var.getName().equals(name)) return var;
		}
		return null;
	}
	
	public Variable setVariable(Variable var)
	{
		Variable has = getVariable(var.getName());

		if(has != null)
		{
			has.set(var.get());
		}
		else
		{
			addVariable(var);
		}

		return var;
	}

	public void resetVariables()
	{
		Variable var;
		for(Enumeration e=variables.elements(); e.hasMoreElements();)
		{
			var = (Variable) e.nextElement();
			var.set(null);
		}
	}

	public static Expression toRPN(Expression exp) throws ProcessorException
	{
		if(exp.getType() == Expression.TYPE_RPN) return exp;

		Lexeme lex;
		Stack stack = new Stack();
		Expression exit = new Expression(Expression.TYPE_RPN);

		// подсчитываем баланс скобок в выражении
		int fs = 0, os = 0;
		for (lex = exp.first(); lex != null; lex = lex.next)
		{
			if(lex instanceof Symbol)
			{
				Symbol s = (Symbol) lex;
				switch(s.getType())
				{
					case Symbol.LEFT_BRACE:
						os++;
						break;

					case Symbol.RIGHT_BRACE:
						os--;
						break;

					case Symbol.LEFT_LIST:
						fs++;
						break;

					case Symbol.RIGHT_LIST:
						fs--;
						break;
				}
			}
		}
		if (fs != 0 || os != 0)
		{
			throw new ProcessorException("Brackets disbalance detected.");
		}

		if(DEBUG) System.out.println("start:");

		for (lex = exp.first(); lex != null; lex = lex.next)
		{
			if(DEBUG)
			{
				String s = "";
				for(int j = 0; j<stack.size(); j++) s += ((Lexeme) stack.elementAt(j)).toString() + ' ';
				
				System.out.println("\tstack: "+s);
				System.out.println("\texit: "+exit);
				System.out.println();
				System.out.println("after read "+lex);
			}

			if(lex instanceof Constant)
				exit.add(((Constant)lex).get());

			else if(lex instanceof Operand)
				exit.add(lex.clone());

			else if(lex instanceof Function)
				stack.push(lex.clone());

			else if(lex instanceof Operator)
			{
				int prior;
				
				while(!stack.empty())
				{
					if(!stack.empty() && stack.lastElement() instanceof Operator) prior =  ((Operator)stack.lastElement()).priority();
					else prior = 0;

					if(((Operator)lex).priority() > prior) break;

					exit.add((Lexeme)stack.pop());
				}
				stack.push(lex.clone());
			}

			else if(lex instanceof Symbol)
			{
				Symbol sym = (Symbol) lex.clone();

				switch(sym.getType())
				{
					case Symbol.LEFT_BRACE:
						stack.push(sym);
						break;

					case Symbol.RIGHT_BRACE:
						while(true)
						{
							if(stack.empty()) throw new ProcessorException("Unable to resolve brackets");
							Lexeme top = (Lexeme) stack.pop();
							if(top instanceof Symbol)
							{
								if(((Symbol)top).getType() == Symbol.LEFT_BRACE) break;
								else exit.add(top);
							}
							else exit.add(top);
						}
						break;

					case Symbol.LEFT_LIST:
						stack.push(sym);
						exit.add(lex.clone());
						break;

					case Symbol.RIGHT_LIST:
						while(true)
						{
							if(stack.empty()) throw new ProcessorException("Unable to resolve brackets");
							Lexeme top = (Lexeme) stack.pop();
							if(top instanceof Symbol)
							{
								if(((Symbol)top).getType() == Symbol.LEFT_LIST) break;
								else exit.add(top);
							}
							else exit.add(top);
						}
						exit.add(lex.clone());
						break;

					case Symbol.COMMA:
						if(stack.empty()) throw new ProcessorException("Unable to resolve comma");
						while(!stack.empty())
						{
							Lexeme l;
							try
							{
								l = (Lexeme) stack.pop();
							}
							catch(EmptyStackException e)
							{
								throw new ProcessorException("Unable to resolve comma");
							}
							if(l instanceof Symbol && ( ((Symbol)l).getType() == Symbol.LEFT_BRACE || ((Symbol)l).getType() == Symbol.LEFT_LIST))
							{
								stack.push(l);
								break;
							}
							exit.add(l);
						}
						break;
				}
			}
		}

		while( !stack.empty() )
		{
			exit.add((Lexeme)stack.pop());
		}

		return exit;
	}

	public void optimize(Expression exp) throws ProcessorException
	{
		for(Lexeme lex = exp.first(); lex != null; lex = lex.next)
		{
			if(lex instanceof Variable)
			{
				Variable var = (Variable) lex;
				Variable cvar = getVariable(var.getName());
				if(cvar == null || cvar.get()==null) throw new ProcessorException("Variable '"+var.getName()+"' not defined.");

				var.set(cvar.get());
			}
		}
	}

	public Operand calculate(Expression in) throws ProcessorException, OperatorException
	{
		Expression exp;
		Stack stack = new Stack();
		Stack lists = new Stack();
		Lexeme lex;

		if(in.getType() == Expression.TYPE_RPN) exp = in;
		else exp = Processor.toRPN(in);
		
		optimize(exp);

		for(lex = exp.first(); lex != null; lex = lex.next)
		{
			if( lex instanceof Variable)
			{
				Variable var = (Variable)lex;
				if(var.get() == null) throw new ProcessorException("Variable '"+var.getName()+"' not defined.");
				else stack.push(var.get());
			}
			else if( lex instanceof Operand )
			{
				stack.push(lex);
			}
			else if( lex instanceof Operator)
			{
				Operator op = (Operator) lex;
				if(stack.size() < op.params()) throw new ProcessorException("Not enough operands for '"+op.toString()+"'.");

				Operand params [] = new Operand[op.params()];
				for(int i=params.length-1; i>=0; i--)
				{
					params[i] = (Operand) stack.pop();
				}

				stack.push(op.exec(params));
			}
			else if(lex instanceof Symbol)
			{
				Symbol sym = (Symbol) lex;

				switch(sym.getType())
				{
					case Symbol.LEFT_LIST:
						lists.push(new List());
						stack.push(sym);
						break;
					
					case Symbol.RIGHT_LIST:
						if(lists.empty()) throw new ProcessorException("Unable to resolve list.");
						List lst = (List) lists.pop();
						while(true)
						{
							Lexeme l = (Lexeme) stack.pop();
							if(l instanceof Operand)
							{
								lst.addToTop((Operand)l);
							}
							else if(l instanceof Symbol && ((Symbol)l).getType() == Symbol.LEFT_LIST)
							{
								stack.push(lst);
								break;
							}
							else
							{
								throw new ProcessorException("Unsupported lexeme for list - '"+l+"'.");
							}
						}
						break;
				}
			}
		}

		if(stack.empty()) throw new ProcessorException("Stack is empty. Something wrong.");
		else return (Operand) stack.pop();
	}
}
